﻿using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine.Networking;

/// <summary>
/// 使用:
///     挂脚本
///     设置模式 UpdateMode,DebugMode,ExtractMode
///     设置回调 NeedUpdateCall,UpdateInfoCall,CompleteCall
///     调用StartUpdate
///     确认是否更新 SureDownload=true
/// </summary>
public class UpdateCmt : MonoBehaviour
{
    public static UpdateCmt Inst;

    /// <summary>更新模式</summary>
    public bool UpdateMode = true;
    /// <summary>调试模式</summary>
    public bool DebugMode = true;
    /// <summary>解压模式</summary>
    public bool ExtractMode = false;
    /// <summary>测试更新地址</summary>
    public string WebUrl = "http://localhost/";
    [HideInInspector]
    /// <summary>要更到的目录</summary>
    public string ToUrl = "";
    /// <summary>更新回调信息  下载阶段,进度百分比,下载M字符</summary>
    public System.Action<int, float,string> UpdateInfoCall;
    /// <summary>更新检查完成回调</summary>
    public System.Action CompleteCall;
    /// <summary>需要更新信息 需要更新的总大小</summary>
    public System.Action<string> NeedUpdateCall;
    /// <summary>确定更新 通常由用户确认后为true[default=false]</summary>
    public bool SureDownload = false;

    /// <summary>
    /// 1 正在解包文件
    /// 2 正在解包文件
    /// 3 解包完成
    /// 11 正在检查更新
    /// 12 正在更新
    /// 13 更新完成
    /// </summary>
    public static int WarnStrFlag = 0;

    /// <summary>下载列表</summary>
    List<UpdateDownloadHandler> mLoadReqes;
    /// <summary>下载数量</summary>
    int dowloadedNum;

    [HideInInspector]
    /// <summary>需要下载的文件大小</summary>
    private long NeedDownSize = 0;
    [HideInInspector]
    /// <summary>当前下载的文件大小</summary>
    private long CurDownSize = 0;

    /// <summary>stream目录的资源清单MD5</summary>
    private Dictionary<string, string> mSteamResMD5;

    /// <summary>
    /// 初始化游戏管理器
    /// </summary>
    void Awake()
    {
        Inst = this;
        ToUrl = UpdateUtil.DataPath;
        //UpdateInfoCall = Test_UpdateInfoCall;
        //CompleteCall = Test_CompleteCall;
        //if (ResourceManager.RemoteModeEnable)
        //{
            SureDownload = true;
            StartUpdate();
        //}
    }

    /// <summary>启动更新</summary>
    public void StartUpdate()
    {
        Init();
    }

    /// <summary>测试更新</summary>
    /// <param name="stat"></param>
    /// <param name="per"></param>
    /// <param name="downStr"></param>
    public void Test_UpdateInfoCall(int stat, float per, string downStr)
    {
        Debug.LogError(stat + " " + per + " " + downStr);
    }

    public void Test_CompleteCall()
    {
        Debug.LogError("update over");
    }

    /// <summary>初始化</summary>
    void Init()
    {
        DontDestroyOnLoad(gameObject);  //防止销毁自己

        dowloadedNum = 0;
        CheckExtractResource(); //释放资源
    }

    /// <summary>释放资源</summary>
    public void CheckExtractResource()
    {
        bool isExists = File.Exists(ToUrl + "files.txt");
        if (isExists || DebugMode)
        {
            if (UpdateMode)
                StartCoroutine(OnUpdateResource());
            else
                OnComplete_UpdateWeb();

            return;   //文件已经解压过了，自己可添加检查文件列表逻辑
        }

        if(ExtractMode)
        {
            StartCoroutine(OnExtractResource());    //启动释放协成 
        }
        else if(UpdateMode)
        {
            OnComplete_UpdateWeb();
        }
    }

    IEnumerator OnExtractResource()
    {
        string dataPath = ToUrl;  //数据目录
        string resPath = UpdateUtil.AppContentPath(); //游戏包资源目录

        dataPath += "/";
        resPath += "/";

        if (Directory.Exists(dataPath)) Directory.Delete(dataPath, true);
        Directory.CreateDirectory(dataPath);

        string infile = resPath + "files.txt";
        string outfile = dataPath + "files.txt";
        if (File.Exists(outfile)) File.Delete(outfile);

        WarnStrFlag = 1;
        SendUpdateInfo(0);
        if (Application.platform == RuntimePlatform.Android)
        {
            UnityWebRequest request = UnityWebRequest.Get(infile);
            yield return request.SendWebRequest();

            if (!Directory.Exists(dataPath))
            {
                Directory.CreateDirectory(dataPath);
            }
            string filesText = request.downloadHandler.text;

            File.WriteAllText(outfile + "files.txt", filesText);

            yield return 0;
        }
        else File.Copy(infile, outfile, true);
        yield return new WaitForEndOfFrame();

        List<DownloadFileInfo> downList = new List<DownloadFileInfo>();
        //释放所有文件到数据目录
        string[] files = File.ReadAllLines(outfile);
        foreach (var file in files)
        {
            string[] fs = file.Split('|');
            infile = resPath + fs[0];  //
            outfile = dataPath + fs[0];

            WarnStrFlag = 2;

            string dir = Path.GetDirectoryName(outfile);
            if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);

            DownloadFileInfo downItm = new DownloadFileInfo();
            downItm.soureFile = infile;
            downItm.downloadUrl = outfile;
            downItm.totalLen = long.Parse(fs[2]);

            downList.Add(downItm);

            NeedDownSize += downItm.totalLen;

            yield return new WaitForEndOfFrame();
        }

        yield return new WaitForSeconds(0.1f);

        StartCoroutine(DownLoad(downList, OnComplete_ExtractResource));
    }

    public void OnComplete_ExtractResource()
    {
        WarnStrFlag = 3;
        SendUpdateInfo(1);
        //释放完成，开始启动更新资源
        StartCoroutine(OnUpdateResource());
    }

    /// <summary>启动更新下载，这里只是个思路演示，此处可启动线程下载更新</summary>
    IEnumerator OnUpdateResource()
    {
        WarnStrFlag = 11;

        string dataPath = ToUrl;  //数据目录
        string url = WebUrl;
        string random = DateTime.Now.ToString("yyyymmddhhmmss");
        string listUrl = url + "files.txt?v=" + random;

        //===========================获取包资源md5清单=======================================
        string steamFiles = Application.streamingAssetsPath + "/files.txt";
        UnityWebRequest steamFilesReq = UnityWebRequest.Get(steamFiles);
        yield return steamFilesReq.SendWebRequest();

        if(string.IsNullOrEmpty(steamFilesReq.error))
        {
            mSteamResMD5 = new Dictionary<string, string>();
            string steamFilesText = steamFilesReq.downloadHandler.text;
            string[] steamfiles = steamFilesText.Split(new char[] { '\r', '\n' }, StringSplitOptions.None);
            int sCnt = steamfiles.Length;
            for (int si = 0; si < sCnt; si++)
            {
                if (string.IsNullOrEmpty(steamfiles[si])) continue;
                string[] keyValue = steamfiles[si].Split('|');

                mSteamResMD5.Add(keyValue[0], keyValue[1]);
            }
        }
      
        //===================================================================

        UnityWebRequest request = UnityWebRequest.Get(listUrl);
        yield return request.SendWebRequest();

        //网络出错打断
        if(!string.IsNullOrEmpty(request.error))
        {
            yield break;
        }

        if (!Directory.Exists(dataPath))
        {
            Directory.CreateDirectory(dataPath);
        }

        string filesText = request.downloadHandler.text;

        File.WriteAllText(dataPath + "/files.txt", filesText);

        string[] files = filesText.Split(new char[] { '\r','\n' }, StringSplitOptions.None);

        List<DownloadFileInfo> dowLoadList = new List<DownloadFileInfo>();

        WarnStrFlag = 12;

        int cnt = files.Length;
        for (int i = 0; i < cnt; i++)
        {
            if (string.IsNullOrEmpty(files[i])) continue;
            string[] keyValue = files[i].Split('|');
            string f = keyValue[0];
            string localfile = (dataPath + "/" + f).Trim();
            string path = Path.GetDirectoryName(localfile);
         
            string fileUrl = url + f + "?v=" + random;
            bool canUpdate = !File.Exists(localfile);
            if (!canUpdate)
            {
                string remoteMd5 = keyValue[1].Trim();
                string localMd5 = UpdateUtil.Md5file(localfile);
                canUpdate = !remoteMd5.Equals(localMd5);
                if (canUpdate) File.Delete(localfile);
                //Debug.Log(localMd5);
            }
            else
            {
                //========================================
                if (SameToSteamFile(f,keyValue[1]))
                    continue;
                //=======================================
            }

            if (canUpdate)
            {   //本地缺少文件
                //Debug.Log(fileUrl);
                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }

                DownloadFileInfo downItm = new DownloadFileInfo();
                downItm.soureFile = fileUrl;
                downItm.downloadUrl = localfile;
                downItm.totalLen = long.Parse(keyValue[2]);

                dowLoadList.Add(downItm);

                NeedDownSize += downItm.totalLen;
            }
        }

        if(NeedDownSize > 0)
        {
            NeedUpdateCall?.Invoke(UpdateUtil.GetFileSize(NeedDownSize));
        }

        yield return new WaitForEndOfFrame();

        //等待用户确定下载
        yield return new WaitUntil(() => {

            return SureDownload;
        });

        StartCoroutine(DownLoad(dowLoadList, OnComplete_UpdateWeb));
    }

    public bool SameToSteamFile(string fileName,string remoteMd5)
    {
        if (mSteamResMD5 == null)
            mSteamResMD5 = new Dictionary<string, string>();

        string steamMd5;
        //远程资源是否和包内资源相同
        if (mSteamResMD5.TryGetValue(fileName, out steamMd5))
        {
            if (steamMd5.CompareTo(remoteMd5) == 0)
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>重Web更新资源完成</summary>
    public void OnComplete_UpdateWeb()
    {
        WarnStrFlag = 13;
       
        SendUpdateInfo(1);

        CompleteCall?.Invoke();

        NeedDownSize = 0;
        CurDownSize = 0;
        mSteamResMD5 = null;
    }

    /// <summary>开始下载资源</summary>
    IEnumerator DownLoad(List<DownloadFileInfo> cFileList, System.Action cComplete)
    {
        mLoadReqes = new List<UpdateDownloadHandler>();

        int cnt = cFileList.Count;
        for (int i = 0; i < cnt; i++)
        {
            DownloadFileInfo fileInfo = cFileList[i];

            string dirName = Path.GetDirectoryName(fileInfo.downloadUrl);

            if (!Directory.Exists(dirName))
                Directory.CreateDirectory(dirName);

            if (!File.Exists(fileInfo.downloadUrl))
            {
                UnityWebRequest request = UnityWebRequest.Get(fileInfo.soureFile);
                UpdateDownloadHandler fHand = new UpdateDownloadHandler(fileInfo.downloadUrl);

                fHand.OwnerRequest = request;
                mLoadReqes.Add(fHand);

                request.SetRequestHeader("Range", "bytes=" + fHand.GetLength() + "-" + fileInfo.totalLen);
                request.downloadHandler = fHand;
                fHand.OnCompleted += val =>
                {
                    request.Dispose();
                    fHand.Dispose();

                    dowloadedNum++;

                    float per = (dowloadedNum) / (cnt * 1.0f);
                    int perVal = (int)(per * 100);
                    per = perVal*0.01f;
                    
                    SendUpdateInfo(per);
                };

                //yield return request.SendWebRequest();
                //var totalLen = long.Parse(request.GetResponseHeader("Content-Length"));
                //var fileLen = fHand.GetLength();
                //var totalLen = fileInfo.totalLen;

                //fHand.Seek(fileLen, SeekOrigin.Begin);

                //request.SetRequestHeader("Range", "bytes=" + fileLen + "-" + totalLen);
                request.SendWebRequest();

                yield return new WaitUntil(() =>
                {
                    CheckDownloaded();
                    return mLoadReqes.Count < 7;
                });
            }
            else
            {
                dowloadedNum++;

                float per = (dowloadedNum) / (cnt * 1.0f);
                int perVal = (int)(per * 100);
                per = perVal * 0.01f;
                SendUpdateInfo(per);
            }
        }


        yield return new WaitUntil(() =>
        {
            CheckDownloaded();
            return mLoadReqes.Count <= 0;
        }
        );

        cComplete();
    }

    void CheckDownloaded()
    {
        int mNt = mLoadReqes.Count;
        for (int mi = 0; mi < mNt; mi++)
        {
            UpdateDownloadHandler handler = mLoadReqes[mi];
            if (handler.mLoadedOldLen != handler.mLoadedLen && handler.totalLength > 0)
            {
                CurDownSize += (long)(handler.mLoadedLen - handler.mLoadedOldLen);
                handler.mLoadedOldLen = handler.mLoadedLen;

                //Debug.LogError(UpdateUtil.GetFileSize((long)curDownSize));
            }

            if (handler.Progress >= 1f && handler.IsComplete())
            {
                handler.DoComplete();
                mLoadReqes.RemoveAt(mi);

                string exName = Path.GetExtension(handler.LoadPath);
                if (exName.CompareTo(".lua") == 0)
                {
                    int luaIndex = handler.LoadPath.IndexOf("Lua/");
                    string luaPath = handler.LoadPath.Substring(luaIndex + 4);
                    string flName = luaPath.Replace(".lua", "");
                    //LuaManager.Require(flName, true);
                }

                break;
            }
        }
    }

    /// <summary>派发更新信息</summary>
    /// <param name="progress">保留2位</param>
    private void SendUpdateInfo(float progress = 0)
    {
        if (NeedDownSize <= 0)
            return;

        string downLoadedStr = UpdateUtil.GetDownLoadedStr(CurDownSize, NeedDownSize);
        UpdateInfoCall?.Invoke(WarnStrFlag, progress, downLoadedStr);
    }


    /// <summary>需要下载的文件大小</summary>
    public long GetNeddDownSize()
    {
        return NeedDownSize;
    }

    /// <summary>当前下载的文件大小</summary>
    public long GetCurDownSize()
    {
        return CurDownSize;
    }

    public void OnDestroy()
    {
        if(mLoadReqes != null)
        {
            int mNt = mLoadReqes.Count;
            for (int mi = 0; mi < mNt; mi++)
            {
                mLoadReqes[mi].Dispose();
                mLoadReqes[mi].OwnerRequest.Dispose();
            }
        }
    }

    //=========================================================================
    /// <summary>获取版本差异列表</summary>
    /// <param name="versionBase"></param>
    /// <param name="versionNew"></param>
    /// <returns></returns>
    public static DictionaryEx<string, S_VersionInfo> GetDissVersionInfo(string versionBase,string versionNew)
    {
        DictionaryEx<string, S_VersionInfo> newData = GetVersionInfo(versionNew);
        DictionaryEx<string, S_VersionInfo> baseData = GetVersionInfo(versionBase);

        DictionaryEx<string, S_VersionInfo> dissData = new DictionaryEx<string, S_VersionInfo>();

        newData.Foreach((itm) => {

            S_VersionInfo newItm = (S_VersionInfo)itm;
            S_VersionInfo baseItm;

            if(baseData.TryGetValue(newItm.Path, out baseItm))
            {
                if (newItm.Hash.CompareTo(baseItm.Hash) != 0)
                {
                    dissData.Add(newItm.Path, newItm);
                }
            }
        });

        return dissData;
    }

    public static DictionaryEx<string,S_VersionInfo> GetVersionInfo(string versionPath)
    {
        DictionaryEx<string, S_VersionInfo> data = new DictionaryEx<string, S_VersionInfo>();

        if (!File.Exists(versionPath))
            return data;

        string filesText = File.ReadAllText(versionPath);

        string[] files = filesText.Split(new char[] { '\r', '\n' }, StringSplitOptions.None);

        int cnt = files.Length;
        for (int i = 0; i < cnt; i++)
        {
            string fs = files[i];
            if (string.IsNullOrEmpty(fs))
                continue;
            S_VersionInfo itm = PraseVersionLine(fs);
            data.Add(itm.Path, itm);
        }

        return data;
    }

    public static S_VersionInfo PraseVersionLine(string versionStr)
    {
        string[] fs = versionStr.Split('|');

        S_VersionInfo itm;
        itm.Path = fs[0];
        itm.Hash = fs[1];
        itm.Length = long.Parse(fs[2]);

        return itm;
    }
}

/// <summary>
/// 下载文件项目信息
/// </summary>
public struct DownloadFileInfo
{
    public string downloadUrl;

    public string soureFile;

    public long totalLen;
}

public struct S_VersionInfo
{
    /// <summary>路径</summary>
    public string Path;
    /// <summary>hash</summary>
    public string Hash;
    /// <summary>总长度</summary>
    public long Length;
}